<?php
namespace ManiaLivePlugins\MLEPP\Dedimania\Runnables;

if(!defined('LF'))
	define('LF', "\n");
if(!defined('CR'))
	define('CR', "\r");
if(!defined('CRLF'))
	define('CRLF', "\r\n");

use ManiaLivePlugins\MLEPP\Dedimania\Response;
use ManiaLivePlugins\MLEPP\Dedimania\Request;
use ManiaLivePlugins\MLEPP\Dedimania\Dedimania;
use ManiaLive\DedicatedApi\Xmlrpc\Request as XmlRpcRequest;
use ManiaLive\DedicatedApi\Xmlrpc\Message;
use ManiaLive\DedicatedApi\Xmlrpc\Base64;
use ManiaLive\Utilities\Logger;

class DedimaniaCall extends \ManiaLive\Threading\Runnable
{
	public $requests;
	public $responses;

	static protected $connection = array();

	/**
	 * @var \ManiaLive\Utilities\Logger
	 */
	static protected $log = null;

	function addRequest($request)
	{
		$this->requests[] = $request;
	}

	function run()
	{
		$calls = array();

		// gets warnings and request time ...
		$this->requests[] = new Request('dedimania.WarningsAndTTR2', array());

		// build multicall param ...
		foreach ($this->requests as $request)
		{
			$calls[] = array(
				'methodName' => $request->name,
				'params' => $request->params
			);
		}

		// build multiquery ...
		$multicall = new XmlRpcRequest('system.multicall', $calls);

		// return result ...
		$results = self::sendRequest($request->port, $request->file, $multicall);

		// reformat response because there can only be one param on first level ...
		$return = array();
		$response = null;
		foreach ($results as $result)
		{
			$response = null;
			if (isset($result['faultCode']) || isset($result['faultString']))
			{
				$response = array
				(
					'Error' => array
					(
						'Message' => $result['faultString'],
						'Code' => $result['faultCode']
					),
					'OK' => false
				);
			}
			else
			{
				if (is_array($result[0]))
				{
					$response = $result[0];
					$response['OK'] = ($result[0] != false && $result[0] != null);
				}
				else
				{
					$response = array();
					$response['OK'] = ($result[0] != false && $result[0] != null);
				}
			}
			$return[] = $response;
		}

		// the last result is dedimania.WarningsAndTTR2 one (because it was added last), it contains 'methods' which is
		// an array of array('methodName'=>methodname,'errors'=>error/waring string,'TTR'=>time-to-respond)
		$results_warnings = end($return);
		if(isset($results_warnings['methods']))
		{
			// add those errors/warnigs in results :
			foreach($results_warnings['methods'] as $index => $method_warnings)
			{
				if(isset($return[$index]) && isset($calls[$index]) && 
					 $method_warnings['methodName'] == $calls[$index]['methodName'] &&
					 $method_warnings['errors'] != '')
					$return[$index]['errors'] = $method_warnings['errors'];
			}
		}

		return $return;
	}

	static function connect($port)
	{
		if (isset(self::$connection[$port])) return self::$connection[$port];

		// dedimania.net
		// http://12.172.123.228:8080
		// open socket, use existing connection if there is one ...
		$errno = 0;
		$errstr = '';
		self::$connection[$port] = pfsockopen('dedimania.net', $port, $errno, $errstr, 30);

		// makes connection unblocking so we can read result ...
		stream_set_blocking(self::$connection[$port], false);

		// connection refused ...
		if (self::$connection[$port] == null) throw new \Exception('Connection failed!');

		// create logfile ...
		self::$log = Logger::getLog('XmlRpc', 'Dedimania');

		return self::$connection[$port];
	}
// urlsafe base64 alternative encode
	static function urlsafe_base64_encode($string) {

	$data = base64_encode($string);
	$data = str_replace(array('+','/','='), array('-','_',''), $data);
	return $data;
}  // urlsafe_base64_encode

// urlsafe base64 alternative decode
	static function urlsafe_base64_decode($string) {

	$data = str_replace(array('-','_'), array('+','/'), $string);
	$mod4 = strlen($data) % 4;
	if ($mod4) {
		$data .= substr('====', $mod4);
	}
	return base64_decode($data);
}  // urlsafe_base64_decode
	/**
	 * Sends request to dedimania network ...
	 * @param \ManiaLive\DedicatedApi\XmlRpc\Request $request
	 */
	static function sendRequest($port, $file, $request)
	{
		$message = $request->getXml();
		$size = $request->getLength();
		$response = self::sendMessage($port, $file, $message);
		$msg = new Message($response);
		if ($msg === false)
		{
			return array();
		}
		$msg->parse();
		return $msg->params[0];
	}

	/**
	 * Sends Xml to the dedimania server using
	 * HTTP request.
	 * @param string $message
	 * @param integer $size
	 */
	static function sendMessage($port, $file, $message)
	{
		$connection = self::connect($port);

		//echo "sendMessage():: {$message}\n";
		
		if ($connection == null)
			throw new \Exception('Not Connected!');
		
		$content = str_replace('&lt;base64&gt;', '<base64>', $message);
		$content = str_replace('&lt;/base64&gt;', '</base64>', $content);
    
		// prepare body ...
		$contents = gzdeflate($content, 9);

		// traffic log
		self::$log->write('REQUEST:' . APP_NL . $message . APP_NL . APP_NL);
        
		echo '[Dedimania] [DEBUG] strlen $contents: '.strlen($contents).CRLF;
        
		// build headers ...
		$headers = 'POST ' . $file . ' HTTP/1.1'.CRLF
		.'Host: dedimania.net'.CRLF
		.'User-Agent: XMLaccess'.CRLF
		.'Cache-Control: no-cache'.CRLF
		.'Content-Encoding: deflate'.CRLF
		.'Content-Type: text/xml; charset=UTF-8'.CRLF
		.'Content-Length: ' . strlen($contents) . CRLF
		.'Content-type: text/xml; charset=UTF-8'. CRLF
		.'Keep-Alive: timeout=600, max=300'.CRLF
		.'Accept-Encoding: deflate'.CRLF
		.'Connection: Keep-Alive'.CRLF;

		// send message
		self::fwrite_stream($connection, $headers . CRLF . $contents);

		// receive response ...
		// start reading header information
		$size = 0;
		while (!feof($connection))
		{
			$line = fgets($connection, 128);

			// read content length ...
			if (($pos = strpos($line, 'Content-Length:')) !== false)
			{
				$size = intval(substr($line, $pos + 15));
			}

			if ($line == CRLF) break;
		}

		// read body ...
		$data = '';
		do
		{
			// wait the socket to be available for read (rather than trying to read again and again when not possible)
      $r = array($connection);
      $w = null;
      $e = null;
	  $nb = false;
      try
		{
			$nb = @stream_select($r, $w, $e, 5);
		}
		catch (\Exception $e)
		{
			if (strpos($e->getMessage(), 'Invalid CRT') !== false)
			{
				$nb = true;
			}
			elseif (strpos($e->getMessage(), 'Interrupted system call') !== false)
			{
				return;
			}
			else
			{
				throw $e;
			}
		}
			if($nb === false){
				// error
				echo "[Dedimania] [ERROR] Received from server: ".strlen($data)." / {$size} :: Error({$errno}) {$errstr}".CRLF;
				return '';
			}
			elseif($nb === 0){
				// because some php versions had erroneous stream_select reply, count them
				$nb = count($r);

				if($nb === 0){
					// still not able to read after the timeout... raise an error ???
					echo "[Dedimania] [ERROR] Received from server: ".strlen($data)." / {$size} :: Error({$errno}) {$errstr}".CRLF;
					return '';
				}
			}

			$line = fgets($connection);
			$data .= $line;
			echo "[Dedimania] [DEBUG] Received from server: ".strlen($data)." / {$size}".CRLF;
		}
		while (strlen($data) < $size);

		// decode body ...
		$body = gzinflate($data);

		// log xmlrpc traffic
		self::$log->write('RESPONSE:' . APP_NL . $body . APP_NL . APP_NL);

		// return body ...
		return $body;
	}

    
	static function fwrite_stream($fp, $string) {
		$slen = strlen($string);
		$nb = false;
		for($written = 0; $written < $slen; $written += $fwrite) {

			// wait the socket to be available for write (rather than trying to send again and again when not possible)
			$r = null;
			$w = array($fp);
			$e = null;
		try
		{
			$nb = @stream_select($r, $w, $e, 5);
		}
		catch (\Exception $e)
		{
			if (strpos($e->getMessage(), 'Invalid CRT') !== false)
			{
				$nb = true;
			}
			elseif (strpos($e->getMessage(), 'Interrupted system call') !== false)
			{
				return;
			}
			else
			{
				throw $e;
			}
		}
			if($nb === false){
				// error
				echo "[Dedimania] [ERROR] Sent to server: {$written} / {$slen} :: Error({$errno}) {$errstr}".CRLF;
				return $written;
			}
			elseif($nb === 0){
				// because some php versions had erroneous stream_select reply, count them
				$nb = count($w);

				if($nb === 0){
					// still not able to write after the timeout... raise an error ???
					echo "[Dedimania] [ERROR] Sent to server: {$written} / {$slen} :: Error({$errno}) {$errstr}".CRLF;
					return $written;
				}
			}

			$fwrite = fwrite($fp, substr($string, $written), 4096);
			if($fwrite === false) {
				echo "[Dedimania] [ERROR] Sent to server: {$written} / {$slen} :: Error({$errno}) {$errstr}".CRLF;
				return $written;
			}
			echo "[Dedimania] [DEBUG] Sent to server: {$fwrite} => ".($written+$fwrite)." / {$slen}".CRLF;

		}
		echo "[Dedimania] [DEBUG] Sent to server done: {$written} / {$slen}".CRLF;
		return $written;
	}
}
?>